在昨天寫完文章時,更加認識了Vue的slot
插槽和當初不太理解的slot props
用法,後來翻了一下Vue的設計模式(Vue Pattern)文章,有提到無渲染元件(renderless component)
,是一種需要結合slot props
的另一種比較常見的應用,今天就來實作和重新理解一下吧。
無渲染元件(renderless component)
slot props
實際應用案例無渲染元件(Renderless component)
是一種 Vue的設計模式,主要概念有點像昨天結尾提到,將元件的商業互動邏輯(Logic)
與UI樣式展示層(View)
完全分離,這樣既可以封裝功能,又不影響視覺呈現。
無渲染元件本身會專注於某些互動行為邏輯上,基本上沒有樣板(Template),而將實際的渲染交由父元件處理
,程式碼上通常只有共用的JS互動邏輯(script)
,這種模式在需要不同 UI 實現,但使用相同邏輯時特別有用,能避免重複編寫相同的邏輯代碼,實現更靈活的重用。
用上次找的無渲染元件文章範例來練習吧~
像下面的Tab切換鍵有3種不一樣的UI款式,功能面上會有點擊事件
和一個切換點擊
的狀態:
一開始我們拿到樣式可能會習慣先開發成3種獨立元件,不過細細分析後會發現在點擊事件
的互動邏輯上,這3種UI元件其實是相同的,它們其實只有template
部分是不一樣的,讓我們步調放慢,一步步重構看起來有點重複性的動作。
首先可以將點擊事件的點選checked狀態
的共用邏輯抽離變成一個組合式函式(composable)
,這麼一來其他元件在用到相同邏輯時就能重複利用,而不是重複地綑在Vue元件檔裡面,這樣我們的第一步checkbox UI重構就算完成。
// useCheckBox.js
import { ref } from "vue";
export function useCheckboxToggle() {
const checkbox = ref(false); // 點擊狀態是否點擊到
// 點擊互動事件
const toggleCheckbox = () => {
checkbox.value = !checkbox.value;
};
return {
checkbox,
toggleCheckbox,
};
}
// 引入checkbox元件中使用
<template>
<div class="comp">
<label class="switch">
<input type="checkbox" :value="checkbox" @click="toggleCheckbox" />
<div class="slider rounded" :class="checkbox ? 'active' : ''"></div>
</label>
</div>
</template>
<script setup>
import { useCheckboxToggle } from "./composables/useCheckboxToggle";
const { checkbox, toggleCheckbox } = useCheckboxToggle();
</script>
上面有提到我們有相同的點擊邏輯,但UI樣版的樣式
要3種,需要開啟給開發者做決定,其實跟上次提到的插槽slot
應用很像,我們可以試著把剛剛重構程式碼變成插槽看看:
// 共用的checkbox UI元件
<template>
<div class="comp">
<slot></slot> // 這裡讓父層決定template要顯示什麼
</div>
</template>
<script setup>
import { useCheckboxToggle } from "./composables/useCheckboxToggle";
const { checkbox, toggleCheckbox } = useCheckboxToggle();
</script>
接著我們就要考量,如何將useCheckboxToggle
的點擊狀態和互動點擊事件,透過插槽props(slots props)
傳遞給父元件,並由父元件負責如何自己定義客製化樣板UI。
<template>
// 這裡連接上子元件定義好的狀態,拋接給外層父元件使用
<slot :checkbox="checkbox" :toggleCheckbox="toggleCheckbox"></slot>
</template>
<script setup>
import { ref } from "vue";
const checkbox = ref(false);
const toggleCheckbox = () => {
checkbox.value = !checkbox.value;
};
</script>
接著就可以統一在父層去設計不同樣式的Tab按鈕UI-範例(可以點進去參考)
<template>
<ToggleComponent v-slot="{ checkbox, toggleCheckbox }">
<div class="comp">
<label class="switch">
<input type="checkbox" :value="checkbox" @click="toggleCheckbox" />
<div class="slider rounded" :class="checkbox ? 'active' : ''"></div>
</label>
</div>
</ToggleComponent>
<!-- Toggle element 2 -->
<ToggleComponent v-slot="{ checkbox, toggleCheckbox }">
<div class="comp">
<button class="toggle-button" @click="toggleCheckbox">
Toggle | <span>{{ checkbox ? "Yes 😀" : "No 😔" }}</span>
</button>
</div>
</ToggleComponent>
</template>
<script setup>
import ToggleComponent from "./components/ToggleComponent";
</script>
其實很多工作上用到的UI元件庫,當我們需要對它進行客製化調整時也是運用同樣slot props
,提供一個窗口給開發者客製化自己的UI元件,但同時保有元件庫提供的互動功能。
像是常用的PrimeVue的客製化分頁器(Paginator):
滿清楚是利用slot props
提供給使用者去接受分頁器裡包裝好的分頁狀態,開發者則可以在slot插槽
內客製化自己想要的UI。
<Paginator :rows="10" :totalRecords="120" :rowsPerPageOptions="[10, 20, 30]">
<template #start="slotProps">
Page: {{ slotProps.state.page }}
First: {{ slotProps.state.first }}
Rows: {{ slotProps.state.rows }}
</template>
<template #end>
<Button type="button" icon="pi pi-search" />
</template>
</Paginator>
無渲染元件
是 Vue 中一種特殊的設計模式,它將互動邏輯
與 UI顯示設計
完全分離。透過這種模式我們可以重用邏輯代碼和提高元件UI變化的靈活性,尤其當有相同互動邏輯需要實現不同 UI ,無渲染元件設計模式很適合呢。
slot props
能夠將一個設計好的元件的狀態與方法傳遞給父層,讓父層能自己決定具體的樣板呈現,實現客製化UI(custom UI),也是目前看到slot props
比較常見的應用(相較於昨天的列表表單顯示)。
例如今天實作的 Tab 切換元件中,我們抽離了共用的點擊切換邏輯
,讓每個不同的 UI 實現只需UI顯示設計
上,這種設計理念同樣也出現在適用於Vue的許多UI 元件庫中。